package com.jl.security;

import java.security.GeneralSecurityException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import com.jl.card.ErrorProcess;
import com.jl.security.pad.IPad;
import com.jl.security.pad.ISO9797M1;
import com.jl.security.pad.ISO9797M2;

public class MyCipher {

	static {
		Crypto.init();
	}

	/** security provider name */
	private final static String providerName = "BC";

	/** default iv value */
	private static final byte[] defaultIV = new byte[] { 0, 0, 0, 0, 0, 0, 0,
			0, };

	public MyCipher() {
	}

	public static byte[] encrypt(String name, byte[] key, byte[] data, byte[] iv) {

		String[] descrit = name.split("/");
		if (descrit.length != 3) {
			ErrorProcess.throwRuntimeException("Crypto name wrong");
		}
		byte[] biv = null;
		if (iv != null && iv.length != 0) {
			biv = iv;
		} else {
			biv = defaultIV;
		}

		String alg = descrit[0].trim();
		String back = descrit[1].trim();
		String pad = descrit[2].trim();

		IvParameterSpec objIv = new IvParameterSpec(biv);
		Key objKey = null;
		Cipher cipher = null;

		try {
			if (alg.startsWith("DES")) {
				if (key.length == 8) {
					alg = "DES";
				} else {
					alg = "DESede";
				}
				objKey = DESKey.getDESKey(key);
				IPad padMode = PadUtil.getInstance(pad);
				if (padMode != null) {
					data = padMode.pad(data);
					pad = "NoPadding";
				}
				cipher = Cipher.getInstance(alg + "/" + back + "/" + pad,
						providerName);
				if (back.equalsIgnoreCase("ECB")) {
					cipher.init(Cipher.ENCRYPT_MODE, objKey);
				} else {
					cipher.init(Cipher.ENCRYPT_MODE, objKey, objIv);
				}
				return cipher.doFinal(data);
			} else if (alg.equals("AES")) {
				objKey = new SecretKeySpec(key, "AES");
				cipher = Cipher.getInstance(alg + "/" + back + "/" + pad,
						providerName);
				if (back.equalsIgnoreCase("ECB")) {
					cipher.init(Cipher.ENCRYPT_MODE, objKey);
				} else {
					cipher.init(Cipher.ENCRYPT_MODE, objKey, objIv);
				}
				return cipher.doFinal(data);
			} else if(alg.equals("RSA")){
				objKey = RSAKey.getRSAKey(key, Cipher.ENCRYPT_MODE);
				cipher = Cipher.getInstance(alg + "/" + back + "/" + pad,
						providerName);
				if (back.equalsIgnoreCase("ECB")) {
					cipher.init(Cipher.ENCRYPT_MODE, objKey);
				} else {
					cipher.init(Cipher.ENCRYPT_MODE, objKey, objIv);
				}
				return cipher.doFinal(data);
			}
		} catch (GeneralSecurityException e) {
			ErrorProcess.throwRuntimeException(e.getMessage());
		}
		return null;

	}

	public static byte[] decrypt(String name, byte[] key, byte[] data, byte[] iv) {
		String[] descrit = name.split("/");
		if (descrit.length != 3) {
			ErrorProcess.throwRuntimeException("Crypto name wrong");
		}
		byte[] biv = null;
		if (iv != null && iv.length != 0) {
			biv = iv;
		} else {
			biv = defaultIV;
		}
		String alg = descrit[0];
		String back = descrit[1];
		String pad = descrit[2];

		IvParameterSpec objIv = new IvParameterSpec(biv);
		Key objKey = null;
		Cipher cipher = null;

		try {
			if (alg.startsWith("DES")) {
				if (key.length == 8) {
					alg = "DES";
				} else {
					alg = "DESede";
				}
				objKey = DESKey.getDESKey(key);
				IPad padmode = PadUtil.getInstance(pad);
				if (padmode != null) {
					pad = "NoPadding";
				}
				cipher = Cipher.getInstance(alg + "/" + back + "/" + pad,
						providerName);
				if (back.equals("ECB")) {
					cipher.init(Cipher.DECRYPT_MODE, objKey);
				} else {
					cipher.init(Cipher.DECRYPT_MODE, objKey, objIv);
				}
				byte[] res = cipher.doFinal(data);
				if (padmode != null) {
					res = padmode.removePad(res);
				}
				return res;
			} else if (alg.equals("AES")) {
				objKey = new SecretKeySpec(key, "AES");
				cipher = Cipher.getInstance(alg + "/" + back + "/" + pad,
						providerName);
				if (back.equalsIgnoreCase("ECB")) {
					cipher.init(Cipher.DECRYPT_MODE, objKey);
				} else {
					cipher.init(Cipher.DECRYPT_MODE, objKey, objIv);
				}
				return cipher.doFinal(data);
			} else if(alg.equals("RSA")){
				objKey = RSAKey.getRSAKey(key, Cipher.DECRYPT_MODE);
				cipher = Cipher.getInstance(alg + "/" + back + "/" + pad,
						providerName);
				if (back.equalsIgnoreCase("ECB")) {
					cipher.init(Cipher.DECRYPT_MODE, objKey);
				} else {
					cipher.init(Cipher.DECRYPT_MODE, objKey, objIv);
				}
				return cipher.doFinal(data);
			}
		} catch (GeneralSecurityException e) {
			ErrorProcess.throwRuntimeException(e.getMessage());
		}
		return null;

	}

	private static class DESKey {

		public static Key getDESKey(byte[] keyVal) {
			String alg = null;
			byte[] key = keyVal.clone();
			if (key.length == 8) {
				alg = "DES";
			} else {
				alg = "DESede";
				if (key.length == 16) {
					key = Util.modifyKey16To24(key);
				}
			}
			SecretKeySpec skey = new SecretKeySpec(key, alg);
			return skey;
		}

	}

	private static class RSAKey {
		public static Key getRSAKey(byte[] originalKey, int mode) {
			try {
				KeyFactory keyFactory = KeyFactory.getInstance("RSA");
				if (mode == Cipher.ENCRYPT_MODE) {
					X509EncodedKeySpec x509eks = new X509EncodedKeySpec(
							originalKey);
					return keyFactory.generatePublic(x509eks);
				} else if (mode == Cipher.DECRYPT_MODE) {
					PKCS8EncodedKeySpec pkcs8eks = new PKCS8EncodedKeySpec(
							originalKey);
					return keyFactory.generatePrivate(pkcs8eks);
				}
			} catch (Exception e) {
				ErrorProcess.throwRuntimeException(e.getMessage());
			}
			return null;
		}
	}

}

class PadUtil {

	public static IPad getInstance(String mode) {
		if (mode.equalsIgnoreCase("ISO9797M2")) {
			return new ISO9797M2();
		} else if (mode.equalsIgnoreCase("ISO9797M1")) {
			return new ISO9797M1();
		} else {
			return null;
		}
	}

}
